﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace System
{
	/// <summary>The LateBoundMethod event handler</summary>
	/// <created author="laurentiu.macovei" date="Sat, 11 Feb 2012 03:13:43 GMT"/>
	public delegate object LateBoundMethod(object target, params object[] arguments);

	/// <summary>Creates fast delegates to methods by using compiled lambda expressions. 
	/// <para>This is faster than reflection if you cache de delegates</para></summary>
	/// <created author="laurentiu.macovei" date="Sat, 11 Feb 2012 03:13:43 GMT"/>
	public static class DelegateFactory
	{

		#region Util Methods

		/// <summary>
		/// </summary>
		/// <param name="method"></param>
		/// <param name="argumentsParameter"></param>
		/// <returns></returns>
		/// <created author="laurentiu.macovei" date="Sat, 11 Feb 2012 03:13:43 GMT"/>
		private static Expression[] CreateParameterExpressions(MethodInfo method, Expression argumentsParameter)
		{
			return method.GetParameters().Select((parameter, index) =>
			  Expression.Convert(
				Expression.ArrayIndex(argumentsParameter, Expression.Constant(index)), parameter.ParameterType)).ToArray();
		}


		#endregion Util Methods


		#region Business Methods

		/// <summary>Creates a compiled lambda expression for the given methodInfo</summary>
		/// <param name="method"></param>
		/// <created author="laurentiu.macovei" date="Sat, 11 Feb 2012 03:13:43 GMT"/>
		public static LateBoundMethod Create(MethodInfo method)
		{
			ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "target");
			ParameterExpression argumentsParameter = Expression.Parameter(typeof(object[]), "arguments");

			MethodCallExpression call = Expression.Call(
			  method.IsStatic ? null : Expression.Convert(instanceParameter, method.DeclaringType),
			  method,
			  CreateParameterExpressions(method, argumentsParameter));

			Expression<LateBoundMethod> lambda = Expression.Lambda<LateBoundMethod>(
			  Expression.Convert(call, typeof(object)),
			  instanceParameter,
			  argumentsParameter);

			return lambda.Compile();
		}


		#endregion Business Methods


	}
}